package cn.jtfadmin.base.doc.swagger;

import com.thoughtworks.qdox.JavaProjectBuilder;
import com.thoughtworks.qdox.model.JavaClass;
import com.thoughtworks.qdox.model.JavaField;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;
import springfox.documentation.builders.ExampleBuilder;
import springfox.documentation.service.AllowableListValues;
import springfox.documentation.service.AllowableValues;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.schema.EnumTypeDeterminer;
import springfox.documentation.spi.service.ExpandedParameterBuilderPlugin;
import springfox.documentation.spi.service.contexts.ParameterExpansionContext;
import springfox.documentation.spring.web.DescriptionResolver;
import springfox.documentation.spring.web.readers.parameter.ModelAttributeParameterMetadataAccessor;
import springfox.documentation.swagger.common.SwaggerPluginSupport;
import springfox.documentation.swagger.schema.ApiModelProperties;

import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Stream;

import static java.util.Optional.ofNullable;
import static java.util.stream.Collectors.toList;
import static org.springframework.util.StringUtils.isEmpty;
import static springfox.documentation.swagger.common.SwaggerPluginSupport.SWAGGER_PLUGIN_ORDER;

@Component
@Order(SWAGGER_PLUGIN_ORDER)
public class DocSwaggerExpandedParameterBuilder implements ExpandedParameterBuilderPlugin {
    private final DescriptionResolver descriptions;
    private final EnumTypeDeterminer enumTypeDeterminer;
    private final JavaProjectBuilder javaProjectBuilder;

    @Autowired
    public DocSwaggerExpandedParameterBuilder(
            JavaProjectBuilder javaProjectBuilder,
            DescriptionResolver descriptions,
            EnumTypeDeterminer enumTypeDeterminer) {
        this.javaProjectBuilder = javaProjectBuilder;
        this.descriptions = descriptions;
        this.enumTypeDeterminer = enumTypeDeterminer;
    }

    @Override
    public void apply(ParameterExpansionContext context) {
        try{
            Field field = ReflectionUtils.findField(context.getClass(), "metadataAccessor");
            ReflectionUtils.makeAccessible(field);
            Object parameterMetadataAccessor = field.get(context);
            if(parameterMetadataAccessor instanceof ModelAttributeParameterMetadataAccessor){
                ModelAttributeParameterMetadataAccessor m = (ModelAttributeParameterMetadataAccessor) parameterMetadataAccessor;
                Field f = ReflectionUtils.findField(ModelAttributeParameterMetadataAccessor.class, "annotatedElements");
                ReflectionUtils.makeAccessible(f);
                List<AnnotatedElement> list = (List<AnnotatedElement>) f.get(m);
                for (AnnotatedElement annotatedElement : list) {
                    if(annotatedElement instanceof Field){
                        Field ann = (Field) annotatedElement;
                        Class<?> declaringClass = ann.getDeclaringClass();
                        JavaClass javaClass = javaProjectBuilder.getClassByName(declaringClass.getName());
                        if(javaClass != null){
                            JavaField fieldByName = javaClass.getFieldByName(context.getFieldName());
                            if(fieldByName !=null) {
                                String comment = fieldByName.getComment();
                                String allowableProperty =
                                        ofNullable("")
                                                .filter(((Predicate<String>) String::isEmpty).negate())
                                                .orElse(null);
                                AllowableValues allowable = allowableValues(
                                        ofNullable(allowableProperty),
                                        context.getFieldType().getErasedType());

                                maybeSetParameterName(context, comment);
                                context.getParameterBuilder()
                                        .description(comment)
                                        .required(false)
                                        .allowableValues(allowable)
                                        .parameterAccess("")
                                        .hidden(false)
                                        .scalarExample("")
                                        .order(SWAGGER_PLUGIN_ORDER)
                                        .build();

                                context.getRequestParameterBuilder()
                                        .description(comment)
                                        .required(false)
                                        .hidden(false)
                                        .example(new ExampleBuilder().value("11").build())
                                        .precedence(SWAGGER_PLUGIN_ORDER)
                                        .query(q -> q.enumerationFacet(e -> e.allowedValues(allowable)));


                            }
                        }
                    }
                }
            }
        }catch (Exception e){
            // ignore
            e.printStackTrace();
        }


    }

    @Override
    public boolean supports(DocumentationType delimiter) {
        return SwaggerPluginSupport.pluginDoesApply(delimiter);
    }


    private void maybeSetParameterName(
            ParameterExpansionContext context,
            String parameterName) {
        if (!isEmpty(parameterName)) {
            context.getParameterBuilder().name(parameterName);
            context.getRequestParameterBuilder().name(parameterName);
        }
    }

    private AllowableValues allowableValues(
            final Optional<String> optionalAllowable,
            Class<?> fieldType) {

        AllowableValues allowable = null;
        if (enumTypeDeterminer.isEnum(fieldType)) {
            allowable = new AllowableListValues(getEnumValues(fieldType), "LIST");
        } else if (optionalAllowable.isPresent()) {
            allowable = ApiModelProperties.allowableValueFromString(optionalAllowable.get());
        }
        return allowable;
    }

    private List<String> getEnumValues(final Class<?> subject) {
        return Stream.of(subject.getEnumConstants())
                .map((Function<Object, String>) Object::toString)
                .collect(toList());
    }
}
